package com.gitee.wsl.text.format

import com.gitee.wsl.ext.base.DOT
import com.gitee.wsl.ext.number.format
import com.gitee.wsl.ext.number.round
import com.gitee.wsl.ext.number.toLongRound
import com.gitee.wsl.ext.string.EMPTY
import com.gitee.wsl.math.Math
import com.gitee.wsl.reflect.ColType
import com.gitee.wsl.text.format.NumberChineseFormatter.format
import com.ionspin.kotlin.bignum.decimal.BigDecimal
import com.ionspin.kotlin.bignum.decimal.DecimalMode
import kotlin.jvm.JvmOverloads


/**
 * 数字转中文类<br></br>
 * 包括：
 * <pre>
 * 1. 数字转中文大写形式，比如一百二十一
 * 2. 数字转金额用的大写形式，比如：壹佰贰拾壹
 * 3. 转金额形式，比如：壹佰贰拾壹整
</pre> *
 *
 * @author fanqun, looly
 */
object NumberChineseFormatter {
    /**
     * 中文形式，奇数位置是简体，偶数位置是记账繁体，0共用<br></br>
     * 使用混合数组提高效率和数组复用
     */
    private val DIGITS = charArrayOf(
        '零', '一', '壹', '二', '贰', '三', '叁', '四', '肆', '五', '伍',
        '六', '陆', '七', '柒', '八', '捌', '九', '玖'
    )

    /**
     * 汉字转阿拉伯数字的
     */
    private val CHINESE_NAME_VALUE = arrayOf<ChineseUnit>(
        ChineseUnit(' ', 1, false),
        ChineseUnit('十', 10, false),
        ChineseUnit('拾', 10, false),
        ChineseUnit('百', 100, false),
        ChineseUnit('佰', 100, false),
        ChineseUnit('千', 1000, false),
        ChineseUnit('仟', 1000, false),
        ChineseUnit('万', 10000, true),
        ChineseUnit('亿', 100000000, true),
    )

    /**
     * 口语化映射
     */
    private val COLLOQUIAL_WORDS = mapOf(
           "一十" to  "十",
           "一拾" to  "拾",
           "负一十" to "负十",
           "负一拾" to "负拾",
    )

    /**
     * 阿拉伯数字转换成中文.
     *
     *
     * 主要是对发票票面金额转换的扩展
     *
     * 如：-12.32
     *
     * 发票票面转换为：(负数)壹拾贰圆叁角贰分
     *
     * 而非：负壹拾贰元叁角贰分
     *
     * 共两点不同：1、(负数) 而非 负；2、圆 而非 元
     * 2022/3/9
     *
     * @param amount           数字
     * @param isUseTraditional 是否使用繁体
     * @param isMoneyMode      是否金额模式
     * @param negativeName     负号转换名称 如：负、(负数)
     * @param unitName         单位名称 如：元、圆
     * @return java.lang.String
     * @author machuanpeng
     * @since 5.7.23
     */
    @JvmOverloads
    fun format(
        amount: Double,
        isUseTraditional: Boolean,
        isMoneyMode: Boolean = false,
        negativeName: String = "负",
        unitName: String = "元",
    ): String {
        var amount = amount
        var unitName = unitName

        if (0.0 == amount) {
            return if (isMoneyMode) "零" + unitName + "整" else "零"
        }

        require(
            amount in  -99999999999999.99..99999999999999.99,
            { "Number support only: (-99999999999999.99 ~ 99999999999999.99)！" }
        )

        val chineseStr = StringBuilder()

        // 负数
        if (amount < 0) {
            chineseStr.append(negativeName)
            amount = -amount
        }

        var yuan: Long = (amount * 100).toLongRound()
        val fen = (yuan % 10).toInt()
        yuan = yuan / 10
        val jiao = (yuan % 10).toInt()
        yuan = yuan / 10

        // 元
        if (!isMoneyMode || 0L != yuan) {
            // 金额模式下，无需“零元”
            chineseStr.append(longToChinese(yuan, isUseTraditional))
            if (isMoneyMode) {
                chineseStr.append(unitName)
            }
        }

        if (0 == jiao && 0 == fen) {
            //无小数部分的金额结尾
            if (isMoneyMode) {
                chineseStr.append("整")
            }
            return chineseStr.toString()
        }

        // 小数部分
        if (!isMoneyMode) {
            chineseStr.append("点")
        }

        // 角
        if (0L == yuan && 0 == jiao) {
            // 元和角都为0时，只有非金额模式下补“零”
            if (!isMoneyMode) {
                chineseStr.append("零")
            }
        } else {
            chineseStr.append(numberToChinese(jiao, isUseTraditional))
            if (isMoneyMode && 0 != jiao) {
                chineseStr.append("角")
            }
        }

        // 分
        if (0 != fen) {
            chineseStr.append(numberToChinese(fen, isUseTraditional))
            if (isMoneyMode) {
                chineseStr.append("分")
            }
        }

        return chineseStr.toString()
    }

    /**
     * 阿拉伯数字（支持正负整数）转换成中文
     *
     * @param amount           数字
     * @param isUseTraditional 是否使用繁体
     * @return 中文
     * @since 5.7.17
     */
    fun format(amount: Long, isUseTraditional: Boolean): String {
        var amount = amount
        if (0L == amount) {
            return "零"
        }

        require(
            amount in  -99999999999999..99999999999999,
            { "Number support only: (-99999999999999.99 ~ 99999999999999.99)！" }
        )

        val chineseStr = StringBuilder()

        // 负数
        if (amount < 0) {
            chineseStr.append("负")
            amount = -amount
        }

        chineseStr.append(longToChinese(amount, isUseTraditional))
        return chineseStr.toString()
    }

    fun format(amount: Int, isUseTraditional: Boolean): String  = format(amount.toLong(),isUseTraditional)

    /**
     * 阿拉伯数字（支持正负整数）四舍五入后转换成中文节权位简洁计数单位，例如 -5_5555 =》 -5.56万
     *
     * @param amount 数字
     * @return 中文
     */
    fun formatSimple(amount: Long): String {
        if (amount < 10000 && amount > -10000) {
            return amount.toString()
        }
        val res: String = if (amount < 100000000 && amount > -100000000) {
            (amount/10000).format(2) + "万"
            //res = NumberUtil.div(amount, 10000, 2) + "万"
        } else if (amount < 1000000000000L && amount > -1000000000000L) {
            (amount/100000000).format(2) + "亿"
            //res = NumberUtil.div(amount, 100000000, 2) + "亿"
        } else {
            (amount/1000000000000L).format(2) + "万亿"
            //res = NumberUtil.div(amount, 1000000000000L, 2) + "万亿"
        }
        return res
    }

    /**
     * 格式化-999~999之间的数字<br></br>
     * 这个方法显示10~19以下的数字时使用"十一"而非"一十一"。
     *
     * @param amount           数字
     * @param isUseTraditional 是否使用繁体
     * @return 中文
     * @since 5.7.17
     */
    fun formatThousand(amount: Int, isUseTraditional: Boolean): String? {
        require(amount in -999..999, { "Number support only: (-999 ~ 999)！" })

        val chinese = thousandToChinese(amount, isUseTraditional)
        if (amount < 20 && amount >= 10) {
            // "十一"而非"一十一"
            return chinese.substring(1)
        }
        return chinese
    }

    /**
     * 阿拉伯数字转换成中文. 使用于整数、小数的转换.
     * 支持多位小数
     *
     * @param amount           数字
     * @param isUseTraditional 是否使用繁体
     * @param isUseColloquial  是否使用口语化(e.g. 一十 -》 十)
     * @return 中文
     * @since 5.8.28
     */
    fun format(amount: BigDecimal, isUseTraditional: Boolean, isUseColloquial: Boolean): String {
        var formatAmount: String
        if (amount.scale <= 0) {
            formatAmount = format(amount.longValue(), isUseTraditional)
        } else {
            val numberList = amount.toPlainString().split(Char.DOT)
            // 小数部分逐个数字转换为汉字
            val decimalPartStr = StringBuilder()
            for (decimalChar in numberList[1].toCharArray()) {
                decimalPartStr.append(numberCharToChinese(decimalChar, isUseTraditional))
            }
            formatAmount = format(amount.longValue(), isUseTraditional) + "点" + decimalPartStr
        }
        if (isUseColloquial) {
            for (colloquialWord in COLLOQUIAL_WORDS.entries) {
                if (formatAmount.startsWith(colloquialWord.key)) {
                    formatAmount =
                        formatAmount.replaceFirst(colloquialWord.key.toRegex(), colloquialWord.value)
                    break
                }
            }
        }
        return formatAmount
    }

    /**
     * 数字字符转中文，非数字字符原样返回
     *
     * @param c                数字字符
     * @param isUseTraditional 是否繁体
     * @return 中文字符
     * @since 5.3.9
     */
    fun numberCharToChinese(c: Char, isUseTraditional: Boolean): String {
        if (c < '0' || c > '9') {
            return c.toString()
        }
        return numberToChinese(c.code - '0'.code, isUseTraditional).toString()
    }

    /**
     * 中文大写数字金额转换为数字，返回结果以元为单位的BigDecimal类型数字
     * 如：
     * “陆万柒仟伍佰伍拾陆元叁角贰分”返回“67556.32”
     * “叁角贰分”返回“0.32”
     *
     * @param chineseMoneyAmount 中文大写数字金额
     * @return 返回结果以元为单位的BigDecimal类型数字
     */
    fun chineseMoneyToNumber(chineseMoneyAmount: String): BigDecimal? {
        if (chineseMoneyAmount.isBlank()) {
            return null
        }

        var yi = chineseMoneyAmount.indexOf("元")
        if (yi == -1) {
            yi = chineseMoneyAmount.indexOf("圆")
        }
        val ji = chineseMoneyAmount.indexOf("角")
        val fi = chineseMoneyAmount.indexOf("分")

        // 先找到单位为元的数字
        var yStr: String? = null
        if (yi > 0) {
            yStr = chineseMoneyAmount.substring(0, yi)
        }

        // 再找到单位为角的数字
        var jStr: String? = null
        if (ji > 0) {
            if (yi >= 0) {
                //前面有元,角肯定要在元后面
                if (ji > yi) {
                    jStr = chineseMoneyAmount.substring(yi + 1, ji)
                }
            } else {
                //没有元，只有角
                jStr = chineseMoneyAmount.substring(0, ji)
            }
        }

        // 再找到单位为分的数字
        var fStr: String? = null
        if (fi > 0) {
            if (ji >= 0) {
                //有角，分肯定在角后面
                if (fi > ji) {
                    fStr = chineseMoneyAmount.substring(ji + 1, fi)
                }
            } else if (yi > 0) {
                //没有角，有元，那就坐元后面找
                if (fi > yi) {
                    fStr = chineseMoneyAmount.substring(yi + 1, fi)
                }
            } else {
                //没有元、角，只有分
                fStr = chineseMoneyAmount.substring(0, fi)
            }
        }

        //元、角、分
        var y = 0
        var j = 0
        var f = 0
        if (!yStr.isNullOrBlank()) {
            y = NumberChineseFormatter.chineseToNumber(yStr)
        }
        if (!jStr.isNullOrBlank()) {
            j = NumberChineseFormatter.chineseToNumber(jStr)
        }
        if (!fStr.isNullOrBlank()) {
            f = NumberChineseFormatter.chineseToNumber(fStr)
        }

        var amount: BigDecimal = BigDecimal.fromInt(y)
        amount = amount.add(BigDecimal.fromInt(j).divide(BigDecimal.TEN, DecimalMode.US_CURRENCY))
        amount = amount.add(BigDecimal.fromInt(f).divide(BigDecimal.fromInt(100), DecimalMode.US_CURRENCY))
        return amount
    }

    /**
     * 阿拉伯数字整数部分转换成中文，只支持正数
     *
     * @param amount           数字
     * @param isUseTraditional 是否使用繁体
     * @return 中文
     */
    private fun longToChinese(amount: Long, isUseTraditional: Boolean): String? {
        var amount = amount
        if (0L == amount) {
            return "零"
        }

        //将数字以万为单位分为多份
        val parts = IntArray(4)
        var i = 0
        while (amount != 0L) {
            parts[i] = (amount % 10000).toInt()
            amount = amount / 10000
            i++
        }

        val chineseStr= StringBuilder()
        var partChinese: String?

        // 千
        var partValue: Int = parts[0]
        if (partValue > 0) {
            partChinese = thousandToChinese(partValue, isUseTraditional)
            chineseStr.insert(0, partChinese)

            if (partValue < 1000) {
                // 和万位之间空0，则补零，如一万零三百
                addPreZero(chineseStr)
            }
        }

        // 万
        partValue = parts[1]
        if (partValue > 0) {
            if ((partValue % 10 == 0 && parts[0] > 0)) {
                // 如果"万"的个位是0，则补零，如十万零八千
                addPreZero(chineseStr)
            }
            partChinese = thousandToChinese(partValue, isUseTraditional)
            chineseStr.insert(0, partChinese + "万")

            if (partValue < 1000) {
                // 和亿位之间空0，则补零，如一亿零三百万
                addPreZero(chineseStr)
            }
        } else {
            addPreZero(chineseStr)
        }

        // 亿
        partValue = parts[2]
        if (partValue > 0) {
            if ((partValue % 10 == 0 && parts[1] > 0)) {
                // 如果"万"的个位是0，则补零，如十万零八千
                addPreZero(chineseStr)
            }

            partChinese = thousandToChinese(partValue, isUseTraditional)
            chineseStr.insert(0, partChinese + "亿")

            if (partValue < 1000) {
                // 和万亿位之间空0，则补零，如一万亿零三百亿
                addPreZero(chineseStr)
            }
        } else {
            addPreZero(chineseStr)
        }

        // 万亿
        partValue = parts[3]
        if (partValue > 0) {
            if (parts[2] == 0) {
                chineseStr.insert(0, "亿")
            }
            partChinese = thousandToChinese(partValue, isUseTraditional)
            chineseStr.insert(0, partChinese + "万")
        }

        if (chineseStr.isNotEmpty() && '零' == chineseStr.get(0)) {
            return chineseStr.substring(1)
        }

        return chineseStr.toString()
    }

    /**
     * 把一个 0~9999 之间的整数转换为汉字的字符串，如果是 0 则返回 ""
     *
     * @param amountPart       数字部分
     * @param isUseTraditional 是否使用繁体单位
     * @return 转换后的汉字
     */
    private fun thousandToChinese(amountPart: Int, isUseTraditional: Boolean): String {
        if (amountPart == 0) {
            // issue#I4R92H@Gitee
            return DIGITS[0].toString()
        }

        var temp = amountPart

        val chineseStr = StringBuilder()
        var lastIsZero = true // 在从低位往高位循环时，记录上一位数字是不是 0
        var i = 0
        while (temp > 0) {
            val digit = temp % 10
            if (digit == 0) { // 取到的数字为 0
                if (!lastIsZero) {
                    // 前一个数字不是 0，则在当前汉字串前加“零”字;
                    chineseStr.insert(0, "零")
                }
                lastIsZero = true
            } else { // 取到的数字不是 0
                chineseStr.insert(
                    0,
                    numberToChinese(digit, isUseTraditional).toString() + getUnitName(i, isUseTraditional)
                )
                lastIsZero = false
            }
            temp = temp / 10
            i++
        }
        return chineseStr.toString()
    }

    /**
     * 把中文转换为数字 如 二百二十 220<br></br>
     *
     *  * 一百一十二 -》 112
     *  * 一千零一十二 -》 1012
     *
     *
     * @param chinese 中文字符
     * @return 数字
     * @since 5.6.0
     */
    fun chineseToNumber(chinese: String): Int {
        val length = chinese.length
        var result = 0

        // 节总和
        var section = 0
        var number = 0
        var unit: ChineseUnit? = null
        var c: Char
        for (i in 0..<length) {
            c = chinese.get(i)
            val num = chineseToNumber(c)
            if (num >= 0) {
                if (num == 0) {
                    // 遇到零时节结束，权位失效，比如两万二零一十
                    if (number > 0 && null != unit) {
                        section += number * (unit.value / 10)
                    }
                    unit = null
                } else if (number > 0) {
                    // 多个数字同时出现，报错
                    throw IllegalArgumentException(
                        String.format(
                            "Bad number '{}{}' at: {}",
                            chinese.get(i - 1),
                            c,
                            i
                        )
                    )
                }
                // 普通数字
                number = num
            } else {
                unit = chineseToUnit(c)
                if (null == unit) {
                    // 出现非法字符
                    throw IllegalArgumentException(String.format("Unknown unit '{}' at: {}", c, i))
                }

                //单位
                if (unit.secUnit) {
                    // 节单位，按照节求和
                    section = (section + number) * unit.value
                    result += section
                    section = 0
                } else {
                    // 非节单位，和单位前的单数字组合为值
                    var unitNumber = number
                    if (0 == number && 0 == i) {
                        // issue#1726，对于单位开头的数组，默认赋予1
                        // 十二 -> 一十二
                        // 百二 -> 一百二
                        unitNumber = 1
                    }
                    section += (unitNumber * unit.value)
                }
                number = 0
            }
        }

        if (number > 0 && null != unit) {
            number = number * (unit.value / 10)
        }

        return result + section + number
    }

    /**
     * 查找对应的权对象
     *
     * @param chinese 中文权位名
     * @return 权对象
     */
    private fun chineseToUnit(chinese: Char): ChineseUnit? {
        for (chineseNameValue in CHINESE_NAME_VALUE) {
            if (chineseNameValue!!.name == chinese) {
                return chineseNameValue
            }
        }
        return null
    }

    /**
     * 将汉字单个数字转换为int类型数字
     *
     * @param chinese 汉字数字，支持简体和繁体
     * @return 数字，-1表示未找到
     * @since 5.6.4
     */
    private fun chineseToNumber(chinese: Char): Int {
        var chinese = chinese
        if ('两' == chinese) {
            // 口语纠正
            chinese = '二'
        }
        val i: Int = DIGITS.indexOf(chinese)
        if (i > 0) {
            return (i + 1) / 2
        }
        return i
    }

    /**
     * 单个数字转汉字
     *
     * @param number           数字
     * @param isUseTraditional 是否使用繁体
     * @return 汉字
     */
    private fun numberToChinese(number: Int, isUseTraditional: Boolean): Char {
        if (0 == number) {
            return DIGITS[0]
        }
        return DIGITS[number * 2 - (if (isUseTraditional) 0 else 1)]
    }

    /**
     * 获取对应级别的单位
     *
     * @param index            级别，0表示各位，1表示十位，2表示百位，以此类推
     * @param isUseTraditional 是否使用繁体
     * @return 单位
     */
    private fun getUnitName(index: Int, isUseTraditional: Boolean): String {
        if (0 == index) {
            return String.EMPTY
        }
        return CHINESE_NAME_VALUE[index * 2 - (if (isUseTraditional) 0 else 1)]!!.name.toString()
    }

    private fun addPreZero(chineseStr: StringBuilder) {
        if (chineseStr.isEmpty()) {
            return
        }
        val c: Char = chineseStr[0]
        if ('零' != c) {
            chineseStr.insert(0, '零')
        }
    }

    /**
     * 权位
     *
     * @author totalo
     * @since 5.6.0
     */
    private class ChineseUnit
    /**
     * 构造
     *
     * @param name    名称
     * @param value   值，即10的倍数
     * @param secUnit 是否为节权位
     */(
        /**
         * 中文权名称
         */
        val name: Char,
        /**
         * 10的倍数值
         */
        val value: Int,
        /**
         * 是否为节权位，它不是与之相邻的数字的倍数，而是整个小节的倍数。<br></br>
         * 例如二十三万，万是节权位，与三无关，而和二十三关联
         */
        val secUnit: Boolean,
    )
}
